package com.yanti.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yanti.common.constant.RedisConstants;
import com.yanti.domain.ResponseResult;
import com.yanti.domain.dto.AnswerDetailDTO;
import com.yanti.domain.dto.UserAnswerSubmitDTO;
import com.yanti.domain.entity.*;
import com.yanti.domain.enums.AppHttpCodeEnum;
import com.yanti.domain.enums.RedisKeyEnum;
import com.yanti.domain.exception.BizException;
import com.yanti.domain.vo.SubjectStudyTimeVo;
import com.yanti.domain.vo.UserExamResultVo;
import com.yanti.filter.LoginUserContextHolder;
import com.yanti.mapper.UserDailyStudyTimeMapper;
import com.yanti.mapper.UserCommitResultsMapper;
import com.yanti.mapper.UserQuestionProgressMapper;
import com.yanti.mapper.UserWrongQuestionsMapper;
import com.yanti.service.*;
import com.yanti.util.BeanCopyUtils;
import com.yanti.util.DateUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * (UserExamResults)表服务实现类
 *
 * @author luo
 * @since 2024-12-11 19:45:19
 */
@EnableScheduling
@Slf4j
@Service("userExamResultsService")
public class UserCommitResultsServiceImpl extends ServiceImpl<UserCommitResultsMapper, UserCommitResults> implements UserCommitResultsService {

    // 注入 RedisTemplate，用于操作 Redis 缓存
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private QuestionsService questionsService;
    @Resource
    private UserAnswerDetailsService userAnswerDetailsService;
    @Resource
    private UserCommitResultsMapper userExamResultMapper;
    @Resource
    private UserDailyStudyTimeMapper userDailyStudyTimeMapper;
    @Resource
    private UserQuestionProgressMapper userQuestionProgressMapper;

    @Resource
    private SubjectsService subjectService;

    @Resource
    private UserDailyStudyTimeService userDailyStudyTimeService;

    @Resource
    private UserWrongQuestionsMapper userWrongQuestionsMapper;

    @Resource
    private UserDailyQuestionCountService userDailyQuestionCountService;
    @Override
    @Transactional // 开启事务
    public ResponseResult userUpLoadAnswer(UserAnswerSubmitDTO submitDTO) {
        // 将当前用户的id设置到submitDTO中
        submitDTO.setUid(Objects.requireNonNull(LoginUserContextHolder.getUserId()).toString());
        // todo 确保健壮性，chapterId与questionId的映射关系，涉及了数据库表字段的问题了，等以后功能扩展吧
        // 获取用户的作答题目id
        List<Long> questionIds = submitDTO.getAnswerDetails().stream()
                .map(AnswerDetailDTO::getQuestionId)
                .toList();
        Map<Long, String> userAnswers = questionsService.getAnswersByIds(questionIds);
        // 获取用户作答答案键值对，键为题目id，值为用户答案
        Map<Long, String> userAnswerMap = submitDTO.getAnswerDetails().stream()
                .collect(Collectors.toMap(AnswerDetailDTO::getQuestionId, AnswerDetailDTO::getUserAnswer));
        // 获取题目id对应的章节id
        Map<Long, String> questionIdChapterMap = questionsService.getChapterIdByQuestionIds(questionIds);
        // 设置对象的属性
        UserCommitResults userCommitResults = new UserCommitResults();
        userCommitResults.setUid(submitDTO.getUid());
        userCommitResults.setSubjectId(submitDTO.getSubjectId());
        userCommitResults.setChapterId(submitDTO.getChapterId());

        // 解析出用户的作答情况
        List<AnswerDetailDTO> answerDetails = submitDTO.getAnswerDetails();
        int totalCount = answerDetails.size();
        userCommitResults.setTotalCount(totalCount);

        int correctCount = 0;
        float totalScore = 0f;
        List<UserAnswerDetail> userAnswerDetails = new ArrayList<>();
        Date now = new Date();
        // 累计本次答题正常用时（不包含暂存用时，先初始化为0）
        long normalTotalStudyTime = 0;
        // 累计本次答题的暂存总用时（从 Redis 获取并累加，先初始化为0）
        long cachedTotalStudyTime = 0;
        //获取暂存题目的逻辑有问题，应该加上题号的，已经改了
        // 先处理暂存题目相关逻辑
        // 查询暂存题目（包括当天暂存和累计暂存）并统计时长等逻辑
        LambdaQueryWrapper<UserAnswerDetail> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserAnswerDetail::getUid, submitDTO.getUid())
                .in(UserAnswerDetail::getQuestionId, questionIds)
                .eq(UserAnswerDetail::getIsTemporary, 1);
        List<UserAnswerDetail> temporaryAnswerDetails = userAnswerDetailsService.list(queryWrapper);

        // 收集所有涉及的题目ID（正常提交）
        Set<Long> allQuestionIds = new HashSet<>(questionIds);
        // 正常提交的题目ID
        answerDetails.forEach(dto -> allQuestionIds.add(dto.getQuestionId()));
//        temporaryAnswerDetails.forEach(detail -> allQuestionIds.add(detail.getQuestionId()));

        // 3. 累加并删除 Redis 中的暂存时间（仅处理本次涉及的题目）
        // todo 这里不需要累加了，之前暂存上传时已经累计了，直接从redis中删除即可，删除的是记录
        deleteRedisQuestionRecords(submitDTO.getUid(), allQuestionIds);
//        cachedTotalStudyTime = sumAndDeleteRedisTimes(submitDTO.getUid(), allQuestionIds);
        // todo 更改了redis的hash结构进行优化
//        // 使用Scan 获取所有符合条件的键
//        String pattern = RedisKeyEnum.TEMPORARY_QUESTION_TIME.getKey(submitDTO.getUid(), "*");
//        log.info("pattern:{}",pattern);
//        long cachedTotalStudyTimeFromRedis = scanAndSumRedisValues(pattern);
//        cachedTotalStudyTime += cachedTotalStudyTimeFromRedis;
//        log.info("累计的时间：cachedTotalStudyTimeFromRedis:{}",cachedTotalStudyTimeFromRedis);

        // 处理正常答题题目逻辑，统计正常答题用时以及构建正式提交的答题详情对象
        for (AnswerDetailDTO answerDetailDTO : answerDetails) {
            // chapterId为空了，已修改
            UserAnswerDetail userAnswerDetail = buildUserAnswerDetail(submitDTO, answerDetailDTO, userAnswers, now);
            userAnswerDetail.setChapterId(Long.valueOf(questionIdChapterMap.get(answerDetailDTO.getQuestionId())));
            userAnswerDetails.add(userAnswerDetail);

            totalScore += userAnswerDetail.getScore();
            if (userAnswerDetail.getIsCorrect() == 1) {
                correctCount++;
            }

            long normalQuestionTime = answerDetailDTO.getEndTimestamp() - answerDetailDTO.getStartTimestamp();
            normalTotalStudyTime += normalQuestionTime;
        }
//        // 先批量保存或更新正常提交的答题详情记录，应该都是新纪录
        userAnswerDetailsService.saveOrUpdateBatch(userAnswerDetails);
        // 累计时间到数据库中
        userDailyStudyTimeService.updateUserDailyStudyTime4DB(submitDTO.getUid(), normalTotalStudyTime,questionIds);
        // 累计时间到缓存中
//        userDailyStudyTimeService.updateUserDailyStudyTime4Cache(submitDTO.getUid(), normalTotalStudyTime);
        // 累计用户刷题数到数据库中
        userDailyQuestionCountService.updateUserDailyQuestionCount4DB(submitDTO.getUid(), questionIds);
        // 累计用户刷题数到缓存中
        // todo 统计刷题数逻辑要改，像刷题时间那样？查询缓存，若缓存不存在，则从数据库中查询
//        userDailyStudyTimeService.updateUserQuestionDailyCount4Cache(submitDTO.getUid(), questionIds);
//        // 存在数据库表的暂存时间
//        int temporaryStudyTime = 0;
        // todo 处理暂存题目的逻辑要大改
        // 处理暂存题目的答题详情对象（更新暂存记录）
        for (UserAnswerDetail temporaryDetail : temporaryAnswerDetails) {
            // todo 检查暂存有无committed标志
            // 设置为已合并到正式记录
            temporaryDetail.setIsCommitted(1);
            // 设置为非暂存
            temporaryDetail.setIsTemporary(0);
            temporaryDetail.setTemporaryType(0);
            // 逻辑删除失败，不能手动设置字段，与mp设计初衷不符
            // 更新答题详情记录
            userAnswerDetailsService.updateById(temporaryDetail);
            // 逻辑删除
            userAnswerDetailsService.removeById(temporaryDetail.getId());
        }

        // 计算答题正确率
        float accuracy = totalCount > 0? (float) correctCount / totalCount * 100 : 0f;
        userCommitResults.setTotalScore(totalScore);
        userCommitResults.setCorrectCount(correctCount);
        userCommitResults.setAccuracy(accuracy);
        userCommitResults.setStatus("已完成");
        userCommitResults.setSubmittedAt(now);
        // 设置本次答题的总刷题时长到UserExamResult中（只有正常答题用时）
        // todo 累计时间要改
        userCommitResults.setTotalStudyTime(normalTotalStudyTime);
        userCommitResults.setCreatedAt(now);
        userCommitResults.setUpdatedAt(now);

        // 保存UserExamResult
        userExamResultMapper.insert(userCommitResults);
        Long userExamResultId = userCommitResults.getId();

        // 设置答题详情的userExamResultId
        userAnswerDetails.forEach(userAnswerDetail -> userAnswerDetail.setUserCommitResultId(userExamResultId));

        // 批量插入或更新用户答题详情（此处主要是批量更新之前保存的正式答题详情记录的userExamResultId等信息）
        userAnswerDetailsService.updateBatchById(userAnswerDetails);

        // 将总刷题时长（毫秒）转换为分钟，赋值给UserExamResultVo的studyTimeInMinutes字段
        // 由分钟改为了秒
        float studyTimeInMinutes = (float) (normalTotalStudyTime) / (1000);
        UserExamResultVo userExamResultVo = BeanCopyUtils.copyBean(userCommitResults, UserExamResultVo.class);
        userExamResultVo.setStudyTimeInMinutes(studyTimeInMinutes);
        // 更新或插入用户每日刷题时间记录（传递正确的总刷题时长）
        // todo 废弃该方法
//        updateOrInsertDailyStudyTime(submitDTO.getUid(), now, normalTotalStudyTime + cachedTotalStudyTime);

        // 更新user_question_progress表中对应题目的完成状态为已完成
        updateQuestionProgressAsCompleted(submitDTO.getUid(), answerDetails.stream().map(AnswerDetailDTO::getQuestionId).collect(Collectors.toList()));

//        // 清理暂存相关数据
//        for (UserAnswerDetail detail : temporaryAnswerDetails) {
//            // todo 随之，获取暂存答案的逻辑也要修改
//            // 如果暂存记录的题目在本次提交中，删除该暂存记录，逻辑删除
//            if(allQuestionIds.contains(detail.getId())){
//                detail.setDeleteFlag(1);
//                userAnswerDetailsService.updateById(detail);
//            }
//        }
        // 收集答错题目的ID，使用stream流完成
        // todo 记录用户错选了哪一个答案？
        List<Long> wrongQuestionIds = answerDetails.stream()
               .filter(answerDetailDTO -> {
                   //判断是否错误
                   return !questionsService.getAnswerByQuestionId(answerDetailDTO.getQuestionId(), answerDetailDTO.getUserAnswer());
               })
               .map(AnswerDetailDTO::getQuestionId)
               .collect(Collectors.toList());
        handleWrongQuestions(submitDTO.getUid(), wrongQuestionIds,userAnswerMap);
        return ResponseResult.okResult(userExamResultVo);
    }

    /**
     * 删除redis中的暂存记录
     * @param uid 用户ID
     * @param allQuestionIds 所有题目ID
     * @return 删除与否
     */
    private boolean deleteRedisQuestionRecords(String uid, Set<Long> allQuestionIds) {
        // Redis存的是hash结构，先构造键，然后使用通配符删除，我的键示例：temp_question_time:100008:2025-05-05，后面还跟了时间
        String pattern = RedisKeyEnum.TEMPORARY_QUESTION_TIME.getKey(uid, "*");
        // redis有可能存了多天的数据，使用通配符，使用不阻塞的方式，使用Scan扫描并删除
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern)
                .count(100)
                .build();
        try (Cursor<String> cursor = redisTemplate.scan(scanOptions)) {
            while (cursor.hasNext()) {
                String key = cursor.next();
                // 删除hash中的数据
                for (Long questionId : allQuestionIds) {
                    redisTemplate.opsForHash().delete(key, questionId.toString());
                }
                // 如果hash为空，删除key
                if (redisTemplate.opsForHash().size(key) == 0) {
                    redisTemplate.delete(key);
                }
            }
        }catch (Exception e){
            log.error("Redis删除暂存题目Hash数据失败", e);
            return false;
        }
        return true;
    }

    // 提取构建UserAnswerDetail对象的方法，使代码更清晰
    private UserAnswerDetail buildUserAnswerDetail(UserAnswerSubmitDTO submitDTO, AnswerDetailDTO answerDetailDTO,
                                                   Map<Long, String> userAnswers, Date now) {
        UserAnswerDetail userAnswerDetail = new UserAnswerDetail();
        userAnswerDetail.setUid(submitDTO.getUid());
        userAnswerDetail.setQuestionId(answerDetailDTO.getQuestionId());
        userAnswerDetail.setUserAnswer(answerDetailDTO.getUserAnswer());
        userAnswerDetail.setIsTemporary(0);

        String correctAnswer = userAnswers.get(answerDetailDTO.getQuestionId());
        boolean isCorrect = correctAnswer!= null && correctAnswer.equals(userAnswerDetail.getUserAnswer());
        userAnswerDetail.setIsCorrect(isCorrect? 1 : 0);

        float score = isCorrect? 1f : 0f;
        userAnswerDetail.setScore(score);

        userAnswerDetail.setStartTimestamp(answerDetailDTO.getStartTimestamp());
        userAnswerDetail.setEndTimestamp(answerDetailDTO.getEndTimestamp());

        userAnswerDetail.setCreatedAt(now);
        userAnswerDetail.setUpdatedAt(now);

        return userAnswerDetail;
    }

    @Override
    @Transactional // 开启事务
    public ResponseResult userTemporarySaveAnswer(UserAnswerSubmitDTO submitDTO) {
        // 将当前用户的id设置到submitDTO中
        submitDTO.setUid(Objects.requireNonNull(LoginUserContextHolder.getUserId()).toString());
        // 获取用户的作答题目id
        List<Long> questionIds = submitDTO.getAnswerDetails().stream()
                .map(AnswerDetailDTO::getQuestionId)
                .toList();
        Map<Long, String> userAnswers = questionsService.getAnswersByIds(questionIds);
        // 获取用户题目id对应的章节id
        Map<Long, String> questionIdChapterIdMap = questionsService.getChapterIdByQuestionIds(questionIds);
        // 解析出用户的作答情况
        List<AnswerDetailDTO> answerDetails = submitDTO.getAnswerDetails();

        List<UserAnswerDetail> userAnswerDetails = new ArrayList<>();
        Date now = new Date();
        // 初始化累计刷题时长
        long cachedTotalStudyTime = 0;
        // 遍历每个答题详情，构建UserAnswerDetail对象
        for (AnswerDetailDTO answerDetailDTO : answerDetails) {
            UserAnswerDetail userAnswerDetail = new UserAnswerDetail();
            userAnswerDetail.setUid(submitDTO.getUid());
            userAnswerDetail.setQuestionId(answerDetailDTO.getQuestionId());
            userAnswerDetail.setUserAnswer(answerDetailDTO.getUserAnswer());
            // chapterId为空了，处理了
            userAnswerDetail.setChapterId(Long.valueOf(questionIdChapterIdMap.get(answerDetailDTO.getQuestionId())));
            // 设置为暂存答案
            userAnswerDetail.setIsTemporary(1);

            // 根据当前时间判断暂存类型（当天暂存或累计暂存）
            Date startOfDay = DateUtils.getStartOfDay(now);
            int temporaryType = now.getTime() >= startOfDay.getTime()? 1 : 2;
            userAnswerDetail.setTemporaryType(temporaryType);

            // 设置开始做该题的时间戳
            userAnswerDetail.setStartTimestamp(answerDetailDTO.getStartTimestamp());

            // 设置做完该题的时间戳
            userAnswerDetail.setEndTimestamp(answerDetailDTO.getEndTimestamp());

            // 计算本题耗时
            userAnswerDetail.setTimeConsuming((int) (answerDetailDTO.getEndTimestamp()
                                -answerDetailDTO.getStartTimestamp()));

            // 获取题目正确答案（此处仅用于可能的前端展示等需求，暂存时不进行对错判断等操作）
            String correctAnswer = userAnswers.get(answerDetailDTO.getQuestionId());

            // 设置其他通用属性，如时间等
            userAnswerDetail.setCreatedAt(now);
            userAnswerDetail.setUpdatedAt(now);
            userAnswerDetail.setTemporaryStartTimestamp(System.currentTimeMillis());

            // 计算本题暂存用时（毫秒），并累加到缓存中对应题目的用时记录
            long questionTime = userAnswerDetail.getEndTimestamp() - userAnswerDetail.getStartTimestamp();
            cachedTotalStudyTime += questionTime;
            // 将本题暂存用时累加到 Redis 中对应题目的累计用时记录上（以题目 ID 作为 Redis 的 key）
            // todo 使用Hash结构存储
            String dateKey = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            String redisHashKey = RedisConstants.buildUserTemporaryStudyTimeKey(submitDTO.getUid(), dateKey);
            // 如果题目没有被暂存过
            if(!redisTemplate.opsForHash().hasKey(redisHashKey, String.valueOf(answerDetailDTO.getQuestionId()))){
                redisTemplate.opsForHash().put(redisHashKey, String.valueOf(answerDetailDTO.getQuestionId()),
                        JSONUtil.toJsonStr(Map.of(
                                "answer", userAnswerDetail.getUserAnswer(),
                                "start", userAnswerDetail.getStartTimestamp(),
                                "end", userAnswerDetail.getEndTimestamp(),
                                "time", userAnswerDetail.getTimeConsuming()
                        )));
                // todo 没有设置过期时间
            }else{
                String jsonStr = (String) redisTemplate.opsForHash().get(redisHashKey, String.valueOf(answerDetailDTO.getQuestionId()));
                // 获取已暂存该题的记录
                Map<String, Object> jsonMap = JSONUtil.toBean(jsonStr, Map.class);
                jsonMap.put("answer", userAnswerDetail.getUserAnswer());
                jsonMap.put("start", userAnswerDetail.getStartTimestamp());
                jsonMap.put("end", userAnswerDetail.getEndTimestamp());
                Object timeObj = jsonMap.get("time");
                int existingTime = 0;
                if (timeObj instanceof Number) {
                    existingTime = ((Number) timeObj).intValue();
                } else if (timeObj instanceof String) {
                    existingTime = Integer.parseInt((String) timeObj);
                }
                jsonMap.put("time", userAnswerDetail.getTimeConsuming()+ existingTime);
                redisTemplate.opsForHash().put(redisHashKey, String.valueOf(answerDetailDTO.getQuestionId()),
                        JSONUtil.toJsonStr(jsonMap));
            }
//            String questionIdStr = String.valueOf(answerDetailDTO.getQuestionId());
//            String cacheKey = "temporary_question_time:" + submitDTO.getUid() + ":" + questionIdStr;
//            Object cachedObj = redisTemplate.opsForValue().get(cacheKey);
//            Long cachedTime = 0L;
//            if (cachedObj instanceof Number) {
//                cachedTime = ((Number) cachedObj).longValue();
//            }
//            redisTemplate.opsForValue().set(cacheKey, cachedTime + questionTime);

            // 如果是当天暂存，在Redis中记录当天暂存题目数（方便后续统计当天刷题数等情况）
            // todo 统计刷题数逻辑要改，仿照正式提交那样
            if (temporaryType == 1) {
                String countKey = RedisConstants.buildUserTodayQuestionCountKey(submitDTO.getUid());
                Object countObj = redisTemplate.opsForValue().get(countKey);
                Long todayTemporaryCount = 0L;
                if (countObj instanceof Number) {
                    todayTemporaryCount = ((Number) countObj).longValue();
                }
                redisTemplate.opsForValue().set(countKey, todayTemporaryCount + 1);
            }
            // 不需要增加今日刷题时间到缓存，因为查询先查询缓存，缓存中不存在再查数据库
//            // 增加今日刷题时间到redis中
//            String todayStudyTimeKey = RedisConstants.buildUserTodayStudyTimeKey(submitDTO.getUid(), dateKey);
//            // 如果是今日首次提交
//            if (Boolean.FALSE.equals(redisTemplate.hasKey(todayStudyTimeKey))) {
//                // todo 这里其实设置了1天过期时间
//                redisTemplate.opsForValue().set(todayStudyTimeKey, questionTime, 1, TimeUnit.DAYS);
//            } else {
//                Object timeObj = redisTemplate.opsForValue().get(todayStudyTimeKey);
//                int existingTime = 0;
//                if (timeObj instanceof Number) {
//                    existingTime = ((Number) timeObj).intValue();
//                } else if (timeObj instanceof String) {
//                    existingTime = Integer.parseInt((String) timeObj);
//                }
//                redisTemplate.opsForValue().set(todayStudyTimeKey, existingTime + questionTime,1, TimeUnit.DAYS);
//            }
            userAnswerDetails.add(userAnswerDetail);
        }
        // todo consuming逻辑需要修改
        // 批量插入或更新用户答题详情（暂存答案逻辑，需要根据is_temporary字段判断处理）
        temporarySaveBatch(userAnswerDetails);
        // todo 更新当日学习时间
        userDailyStudyTimeService.updateUserDailyStudyTime4DB(submitDTO.getUid(), cachedTotalStudyTime, questionIds);
        // 同时对缓存进行更新，实现双写更新
        // todo 这里为什么注释了。。。
//        userDailyStudyTimeService.updateUserDailyStudyTime4Cache(submitDTO.getUid(), cachedTotalStudyTime);
        // 更新user_question_progress表中对应题目的完成状态为未完成（因为只是暂存，不算完成题目）
        updateQuestionProgressAsUncompleted(submitDTO.getUid(), answerDetails.stream().map(AnswerDetailDTO::getQuestionId).collect(Collectors.toList()));

        return ResponseResult.okResult("答案暂存成功");
    }

    public boolean isChapterCompleted(String userId, String subjectId, String chapterId) {
        QueryWrapper<UserQuestionProgress> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userId).eq("subject_id", subjectId).eq("chapterId", chapterId).eq("is_completed", 0);
        int incompleteCount = Math.toIntExact(userQuestionProgressMapper.selectCount(queryWrapper));
        return incompleteCount == 0;
    }

    /**
     * 获取用户当天每一个科目的刷题时间，即408的4本书的刷题时间
     * @param uid 用户uid
     * @return 当天各科刷题时间
     */
    @Override
    public List<SubjectStudyTimeVo> getSubjectStudyTimeToday(String uid) {
        // 先获取所有科目信息
        List<Subjects> allSubjects = subjectService.getAllSubjects();

        // 用于累计每个科目的学习时间（以科目ID为键），初始化为0
        Map<String, Long> subjectTimeMap = allSubjects.stream()
                .collect(Collectors.toMap(subject -> subject.getId().toString(), s -> 0L));

        // 获取当天日期（使用系统默认时区）
        LocalDate today = LocalDate.now(ZoneId.systemDefault());
        LocalDateTime startOfDay = today.atStartOfDay();
        LocalDateTime endOfDay = LocalDateTime.of(today, LocalTime.MAX);
        java.sql.Timestamp startTimestamp = java.sql.Timestamp.valueOf(startOfDay);
        java.sql.Timestamp endTimestamp = java.sql.Timestamp.valueOf(endOfDay);

        // 查询当天有答题记录的 UserAnswerDetail（正常提交和暂存的都算，因为都涉及学习时间）
        QueryWrapper<UserAnswerDetail> userAnswerDetailQueryWrapper = new QueryWrapper<>();
        userAnswerDetailQueryWrapper.eq("uid", uid)
                .ge("created_at", startTimestamp)
                .le("created_at", endTimestamp);
//                // 排除暂存记录，只查询正常提交的记录，不然与redis的暂存时间会重复计算
//                .ne("user_commit_result_id",-1);
        List<UserAnswerDetail> userAnswerDetails = userAnswerDetailsService.list(userAnswerDetailQueryWrapper);

        // 遍历当天所有答题记录，获取对应的题目信息，累加每个科目的学习时间
        for (UserAnswerDetail userAnswerDetail : userAnswerDetails) {
            Long questionId = userAnswerDetail.getQuestionId();
            Questions question = questionsService.getById(questionId);
            String subjectId = question.getSubjectId();
            long questionStudyTime = userAnswerDetail.getEndTimestamp() - userAnswerDetail.getStartTimestamp();

            subjectTimeMap.put(subjectId, subjectTimeMap.getOrDefault(subjectId, 0L) + questionStudyTime);
        }

//        // 生成Redis键匹配模式
//        String pattern = RedisKeyEnum.TEMPORARY_QUESTION_TIME.getKey(uid, "*");
//
//        // 使用 SCAN 命令安全遍历 Redis 键并累加值
//        Set<String> cachedQuestionTimeKeys = scanKeysSafely(pattern);
//        log.info("cachedQuestionTimeKeys:{}",cachedQuestionTimeKeys);
//        // 处理每个暂存题目时间键
//        if(!CollectionUtils.isEmpty(cachedQuestionTimeKeys)){
//            for (String key : cachedQuestionTimeKeys) {
//                String[] parts = key.split(":");
//                long questionId = Long.parseLong(parts[2]);
//
//                Questions question = questionsService.getById(questionId);
//                String subjectId = question.getSubjectId();
//                Long cacheQuestionTime = getRedisValueAsLong(key);
//                subjectTimeMap.put(subjectId, subjectTimeMap.getOrDefault(subjectId, 0L) + cacheQuestionTime);
//            }
//        }

//        // 再处理暂存题目对应的学习时间（从Redis中获取并累加,暂存题目时间记录在Redis中）
//        Set<String> cachedQuestionTimeKeys = redisTemplate.keys("temporary_question_time:" + uid + ":*");
//        if (cachedQuestionTimeKeys!= null &&!cachedQuestionTimeKeys.isEmpty()) {
//            for (String key : cachedQuestionTimeKeys) {
//                String[] parts = key.split(":");
//                String questionIdStr = parts[2];
//                Long questionId = Long.parseLong(questionIdStr);
//                Questions question = questionsService.getById(questionId);
//                String subjectId = question.getSubjectId();
//
//                Object cachedObj = redisTemplate.opsForValue().get(key);
//                Long cachedQuestionTime = 0L;
//                if (cachedObj instanceof Number) {
//                    cachedQuestionTime = ((Number) cachedObj).longValue();
//                }
//
//                subjectTimeMap.put(subjectId, subjectTimeMap.getOrDefault(subjectId, 0L) + cachedQuestionTime);
//            }
//        }

        // 将毫秒单位的时间转换为分钟，并封装成 SubjectStudyTimeVo 对象列表
        List<SubjectStudyTimeVo> resultList = new ArrayList<>();
        for (Subjects subject : allSubjects) {
            long studyTimeInMinutes = subjectTimeMap.getOrDefault(subject.getId().toString(), 0L) / (1000 * 60);
            resultList.add(new SubjectStudyTimeVo(subject.getId().toString(), subject.getName(), studyTimeInMinutes));
        }

        return resultList;
    }

    @Override
    public ResponseResult getTemporaryAnswerDetails(String uid, String chapterId) {
        // 查询用户暂存答题记录
        //todo: 暂存记录的查询条件需要完善
        QueryWrapper<UserAnswerDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", uid)
                .eq("is_committed", 0)
                .eq("chapter_id", chapterId)
                .eq("is_temporary", 1);
        List<UserAnswerDetail> temporaryAnswerDetails = userAnswerDetailsService.list(queryWrapper);

        List<Map<String, Object>> resultList = temporaryAnswerDetails.stream()
                .map(detail -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("questionId", detail.getQuestionId());
                    map.put("userAnswer", detail.getUserAnswer());
                    map.put("startTimestamp", detail.getStartTimestamp());
                    map.put("endTimestamp", detail.getEndTimestamp());
                    map.put("temporaryType", detail.getTemporaryType());
                    return map;
                })
                .collect(Collectors.toList());

        return ResponseResult.okResult(resultList);
    }

    /**
     * 批量插入或更新用户答题详情（针对暂存答案情况），在user_answer_details表中插入或更新数据，根据是否已存在记录以及is_temporary字段进行处理
     *
     * @param userAnswerDetails 包含用户暂存答案信息的列表
     */
    private void temporarySaveBatch(List<UserAnswerDetail> userAnswerDetails) {
        for (UserAnswerDetail userAnswerDetail : userAnswerDetails) {
            QueryWrapper<UserAnswerDetail> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("uid", userAnswerDetail.getUid()).eq("question_id", userAnswerDetail.getQuestionId()).eq("is_temporary", 1);
            UserAnswerDetail existingRecord = userAnswerDetailsService.getOne(queryWrapper);
            if (existingRecord!= null) {
                // 如果存在，比较时间戳，保留最新的答案
                if (existingRecord.getTemporaryStartTimestamp() < userAnswerDetail.getTemporaryStartTimestamp()) {
                    existingRecord.setUserAnswer(userAnswerDetail.getUserAnswer());
                    existingRecord.setStartTimestamp(userAnswerDetail.getStartTimestamp());
                    existingRecord.setEndTimestamp(userAnswerDetail.getEndTimestamp());
                    // 更新时间消耗, 累加时间消耗,因为时间消耗是累加的
                    existingRecord.setTimeConsuming(existingRecord.getTimeConsuming()+userAnswerDetail.getTimeConsuming());
                    existingRecord.setIsCorrect(userAnswerDetail.getIsCorrect());
                    existingRecord.setScore(userAnswerDetail.getScore());
                    existingRecord.setIsCommitted(0);
                    existingRecord.setUpdatedAt(new Date());
                    try {
                        userAnswerDetailsService.updateById(existingRecord);
                    } catch (Exception e) {
                        // 处理更新异常
                        log.error("Failed to update existing temporary answer record: {}", e.getMessage(), e);
                        throw new BizException(AppHttpCodeEnum.TEMPORARY_COMMIT_FAILED);
                    }
                }
            } else {
                // 设置userExamResultId为约定的默认值（-1，表示暂存答案）
                userAnswerDetail.setUserCommitResultId(-1L);
                userAnswerDetail.setIsTemporary(1);
                userAnswerDetail.setIsCommitted(0);
                userAnswerDetail.setCreatedAt(new Date());
                userAnswerDetail.setUpdatedAt(new Date());
                try {
                    userAnswerDetailsService.save(userAnswerDetail);
                } catch (Exception e) {
                    // 处理插入异常
                    log.error("Failed to insert new temporary answer record: {}", e.getMessage(), e);
                    throw new BizException(AppHttpCodeEnum.TEMPORARY_COMMIT_FAILED);
                }
            }
        }
    }

    /**
     * 将指定用户、题目列表在user_question_progress表中的完成状态更新为未完成
     *
     * @param uid 用户ID
     * @param questionIds 题目ID列表
     */
    private void updateQuestionProgressAsUncompleted(String uid, List<Long> questionIds) {
        for (Long questionId : questionIds) {
            QueryWrapper<UserQuestionProgress> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("uid", uid).eq("question_id", questionId);
            UserQuestionProgress progress = userQuestionProgressMapper.selectOne(queryWrapper);
            if (progress == null) {
                // 如果不存在记录，则插入新记录
                UserQuestionProgress newProgress = new UserQuestionProgress();
                newProgress.setUid(uid);
                newProgress.setQuestionId(String.valueOf(questionId));
                // 同样假设能获取到正确的subjectId和chapterId并传入
                Questions question = questionsService.getById(questionId);
                newProgress.setSubjectId(question.getSubjectId());
                newProgress.setChapterId(question.getChapterId());
                newProgress.setIsCompleted(0);
                LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
                Date currentDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
                newProgress.setCreatedAt(currentDate);
                newProgress.setUpdatedAt(currentDate);
                userQuestionProgressMapper.insert(newProgress);
            } else {
                // 如果已存在记录，更新完成状态为未完成
                progress.setIsCompleted(0);
                userQuestionProgressMapper.updateById(progress);
            }
        }
    }

    /**
     * 根据用户ID、日期以及本次答题总时长，更新或插入用户每日刷题时间记录
     *
     * @param userId 用户ID
     * @param date 当前答题日期
     * @param studyTime 本次答题总时长（单位与时间戳一致，如毫秒）
     */
    private void updateOrInsertDailyStudyTime(String userId, Date date, long studyTime) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String formattedDate = sdf.format(date);
        try {
            Date parsedDate = sdf.parse(formattedDate);
            Timestamp timestamp = new Timestamp(parsedDate.getTime());
            QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("uid", userId).eq("date", timestamp);
            UserDailyStudyTime record = userDailyStudyTimeMapper.selectOne(queryWrapper);
            if (record!= null) {
                record.setTotalStudyTime(record.getTotalStudyTime() + studyTime);
                userDailyStudyTimeMapper.updateById(record);
            } else {
                UserDailyStudyTime newRecord = new UserDailyStudyTime();
                newRecord.setUid(userId);
                newRecord.setDate(timestamp);
                newRecord.setTotalStudyTime(studyTime);
                userDailyStudyTimeMapper.insert(newRecord);
            }
        } catch (Exception e) {
            log.error("更新或插入每日刷题时间记录失败: {}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 更新用户完成状态
     * @param uid
     * @param questionIds
     */
    private void updateQuestionProgressAsCompleted(String uid, List<Long> questionIds) {
        for (Long questionId : questionIds) {
            QueryWrapper<UserQuestionProgress> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("uid", uid).eq("question_id", questionId);
            UserQuestionProgress progress = userQuestionProgressMapper.selectOne(queryWrapper);
            if (progress == null) {
                // 如果不存在记录，则插入新记录
                UserQuestionProgress newProgress = new UserQuestionProgress();
                newProgress.setUid(uid);
                newProgress.setQuestionId(String.valueOf(questionId));
                Questions question = questionsService.getById(questionId);
                newProgress.setSubjectId(question.getSubjectId());
                newProgress.setChapterId(question.getChapterId());
                newProgress.setIsCompleted(1);
                // 设置当前时间
                LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
                Date currentDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
                newProgress.setCreatedAt(currentDate);
                newProgress.setUpdatedAt(currentDate);
                userQuestionProgressMapper.insert(newProgress);
            } else {
                // 如果已存在记录，更新完成状态为已完成
                progress.setIsCompleted(1);
                userQuestionProgressMapper.updateById(progress);
            }
        }
    }

    // 每天12点执行删除today_temporary_count相关键值对的任务
    // 每天12点执行删除today_temporary_count相关键值对的任务，使用Redis管道提升删除效率
    @Scheduled(cron = "0 0 12 * * *")
    public void clearTodayTemporaryCount() {
        String pattern = RedisKeyEnum.TODAY_TEMPORARY_COUNT.getKey("*");
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern)
                .count(100) // 每批次扫描500个键，根据数据量调整
                .build();
        // 1. 分批次 SCAN 遍历所有匹配的键
        Set<String> keysToDelete = new HashSet<>();
        try (Cursor<String> cursor = redisTemplate.scan(scanOptions)) {
            while (cursor.hasNext()) {
                keysToDelete.add(cursor.next());
            }
        } catch (Exception e) {
            log.error("SCAN遍历失败: {}", e.getMessage());
            return;
        }
        // 2. 使用管道批量删除（提升性能）
        if (!keysToDelete.isEmpty()) {
            redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                keysToDelete.forEach(key -> connection.unlink(key.getBytes())); // 异步删除，非阻塞
                return null;
            });
            log.info("成功清理 {} 个临时计数键", keysToDelete.size());
        }
    }
    private long scanAndSumRedisValues(String pattern) {
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern)
                .count(100)
                .build();

        Cursor<String> cursor = redisTemplate.scan(scanOptions);
        long sum = 0;
        try {
            while (cursor.hasNext()) {
                String key = cursor.next();
                Object value = redisTemplate.opsForValue().get(key);
                sum += userDailyStudyTimeService.parseRedisValueToLong(value);
                redisTemplate.delete(key); // 清理暂存数据
            }
        } finally {
            cursor.close();
        }
        return sum;
    }

    /**
     *
     * @param uid 用户id
     * @param questionIds 题目id
     * @return
     */
    private long sumAndDeleteRedisTimes(String uid, Set<Long> questionIds) {
        long totalTime = 0;
        List<String> keysToDelete = new ArrayList<>();

        for (Long questionId : questionIds) {
            // 生成符合 RedisKeyEnum 的键
            String key = RedisKeyEnum.TEMPORARY_QUESTION_TIME.getKey(uid, questionId.toString());
            Object value = redisTemplate.opsForValue().get(key);
            totalTime += userDailyStudyTimeService.parseRedisValueToLong(value);
            keysToDelete.add(key);
        }

        // 批量删除键（若存在）
        if (!keysToDelete.isEmpty()) {
            redisTemplate.delete(keysToDelete);
        }
        return totalTime;
    }

    /**
     *  处理错题记录
     *  todo得获取用户错了的答案
     * @param uid 用户id
     * @param wrongQuestionIds 错题id
     */
    private void handleWrongQuestions(String uid, List<Long> wrongQuestionIds,Map<Long, String> userAnswers) {
        if (CollectionUtils.isEmpty(wrongQuestionIds)) {
            return;
        }

        // 1. 批量获取题目完整信息
        List<Questions> questions = questionsService.listByIds(wrongQuestionIds);
        Map<Long, Questions> questionMap = questions.stream()
                .collect(Collectors.toMap(Questions::getId, Function.identity()));

        // 2. 构建错题记录列表
        List<UserWrongQuestions> wrongQuestions = new ArrayList<>();
        Date now = new Date();

        for (Long questionId : wrongQuestionIds) {
            Questions question = questionMap.get(questionId);
            if (question != null) {
                UserWrongQuestions record = new UserWrongQuestions();
                record.setUid(uid);
                record.setQuestionId(questionId);
                record.setSubjectId(Long.valueOf(question.getSubjectId()));
                record.setChapterId(Long.valueOf(question.getChapterId()));
                // 设置用户的答案
                record.setUserAnswer(userAnswers.get(questionId));
                record.setDifficulty(question.getDifficulty());
                record.setErrorCount(1);
                record.setLastPracticedAt(now);
                record.setCreatedAt(now);
                record.setUpdatedAt(now);
                record.setIsMastered(1);
                wrongQuestions.add(record);
            }
        }

        // 3. 批量插入或更新（存在则error_count+1）
        if (!wrongQuestions.isEmpty()) {
            userWrongQuestionsMapper.insertOrUpdateBatch(wrongQuestions);
        }
    }

    /**
     * 使用 SCAN 命令安全遍历 Redis 键（修复键格式问题）
     * @param pattern Redis 键的匹配模式 例如："temporary_question_time:user1:*"
     * 注意：pattern 中的 * 表示通配符，用于匹配任意字符
     * return 键集合
     */
    private Set<String> scanKeysSafely(String pattern) {
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern)
                .count(100)
                .build();
        Set<String> keys = new HashSet<>();
        try (Cursor<String> cursor = redisTemplate.scan(scanOptions)) {
            while (cursor.hasNext()) {
                keys.add(cursor.next());
            }
        }catch (Exception e){
            log.error("SCAN遍历失败: {}",e.getMessage());
        }
        return keys;
    }

    /**
     * 获取Redis值并转换为Long类型
     * @param key redis键
     * @return redis值
     */
    private Long getRedisValueAsLong(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            try {
                return Long.parseLong((String) value);
            } catch (NumberFormatException e) {
                log.warn("非数值类型值: Key={}, Value={}", key, value);
            }
        }
        return 0L;
    }
}